/*
 *	ME2 C++ SDK
 *	Copyright (c) 2010 _silencer
 *
 *	Permission is hereby granted, free of charge, to any person
 *	obtaining a copy of this software and associated documentation
 *	files (the "Software"), to deal in the Software without
 *	restriction, including without limitation the rights to use,
 *	copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the
 *	Software is furnished to do so, subject to the following
 *	conditions:
 *
 *	The above copyright notice and this permission notice shall be
 *	included in all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *	OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef UNREAL_OBJECT_H
#define UNREAL_OBJECT_H

#include "unreal_core.h"
#include "unreal_name.h"

namespace unreal
{
	#pragma pack(push)
	#pragma pack(1)

	uclass* get_package_class( char* class_name, char* package_name );

	#define DECLARE_CORE_CLASS( type, base, name ) \
	private: \
		static uclass* static_class_##type; \
		static uname static_class_##type##_name; \
		static const char* static_class_##type##_str; \
		static const char* static_package_##type##_str; \
	public: \
		typedef type this_type; \
		typedef base super_type; \
		static uclass* static_get_class() \
		{ \
			if ( static_class_##type == 0 ) \
				static_class_##type = get_package_class( #name, "Core" ); \
			return static_class_##type; \
		} \
		static uname static_get_class_name() \
		{ \
			if ( static_class_##type##_name.is_none() ) \
				static_class_##type##_name = find_global_name( #name ); \
			return static_class_##type##_name; \
		} \
		static const char* static_get_class_str() \
		{ \
			return static_class_##type##_str; \
		} \
		static const char* static_get_package_str() \
		{ \
			return static_package_##type##_str; \
		}

	#define IMPLEMENT_CORE_CLASS( type, base, name ) \
	uclass* type##::static_class_##type; \
	uname type##::static_class_##type##_name; \
	const char* type##::static_class_##type##_str = #name; \
	const char* type##::static_package_##type##_str = "Core";

	#define DECLARE_PACKAGE_CLASS( type, base, package ) \
	private: \
		static uclass* static_class_##type; \
		static uname static_class_##type##_name; \
		static const char* static_class_##type##_str; \
		static const char* static_package_##type##_str; \
	public: \
		typedef type this_type; \
		typedef base super_type; \
		static uclass* static_get_class() \
		{ \
		if ( static_class_##type == 0 ) \
		static_class_##type = get_package_class( #type, #package ); \
		return static_class_##type; \
		} \
		static uname static_get_class_name() \
		{ \
		if ( static_class_##type##_name.is_none() ) \
		static_class_##type##_name = find_global_name( #type ); \
		return static_class_##type##_name; \
		} \
		static const char* static_get_class_str() \
		{ \
		return static_class_##type##_str; \
		} \
		static const char* static_get_package_str() \
		{ \
			return static_package_##type##_str; \
		}

	#define IMPLEMENT_PACKAGE_CLASS( type, base, package ) \
		uclass* type##::static_class_##type; \
		uname type##::static_class_##type##_name; \
		const char* type##::static_class_##type##_str = #type; \
		const char* type##::static_package_##type##_str = #package;

	typedef void (__thiscall *process_event_type)( uobject* _this, ufunction* function, void* parms, void* result );

	/*
		TODO add object flags enum and check them!
	 */

	class uobject
	{
		DECLARE_CORE_CLASS( uobject, uobject, Object )

	public:

		void call_function( ufunction* function, void* parms, void* result );
		std::string get_path_name( const uobject* last_outer ) const;
		std::string get_full_name() const;
		bool is_a( uclass* base ) const;
		bool is_in( uobject* outer ) const;

		bool is_valid() const
		{
			return this && table_->is_valid( index_ ) && ( (*table_)[index_] == this );
		}

		uint32 get_index() const
		{
			return index_;
		}

		uint64 get_object_flags() const
		{
			return object_flags_;
		}

		uobject* get_outer() const
		{
			return outer_;
		}

		uobject* get_upper_outer() const;

		uclass* get_class() const
		{
			return class_;
		}

		uobject* get_arche_type() const
		{
			return object_arche_type_;
		}

		const char* get_name() const
		{
			if ( name_.is_valid() )
				return *name_;

			return "None";
		}

		const unsigned int get_name_index() const
		{
			return name_.get_index();
		}

		void set_object_flags( const uint64 object_flags )
		{
			object_flags_ = object_flags;
		}

		friend uclass* get_package_class( char* class_name, char* package_name );

		template <typename _T>
		friend _T* find_global_object( const uname& has_name, uclass* has_class, uobject* has_outer );

		template <typename _T>
		friend _T* find_global_object( const std::string& name );

		friend class uengine_interface;
		friend class usdk_collector;

	private:

		static uarray<uobject*>* table_;
		static process_event_type process_event_;

		void** object_vtable_;
		uint32 index_;
		uint64 object_flags_;
		uint32 hash_next_;
		uint32 hash_outer_next_;
		uint32 state_frame_;
		uobject* linker_;
		uint32 linker_index_;
		uint32 net_index_;
		uobject* outer_;
		uname name_;
		uint32 pad_;
		uclass* class_;
		uobject* object_arche_type_;
	};

	#pragma pack(pop)

	template <typename _T>
	_T* find_global_object( const uname& has_name, uclass* has_class = _T::static_get_class(), uobject* has_outer = 0 )
	{
		for ( unsigned int i( 0 ); i < uobject::table_->size(); i++ )
		{
			uobject* object( (*uobject::table_)[i] );

			if ( object->is_valid() )
			{
				//bool same_name( boost::iequals( *has_name, object->get_name() ) );
				bool same_name( has_name.get_index() == object->get_name_index() );

				if ( same_name )
				{
					bool same_class( has_class ? object->get_class() == has_class : true );
					bool same_outer( has_outer ? object->get_outer() == has_outer : true );

					if ( same_class && same_outer )
						return reinterpret_cast<_T*>(object);
				}
			}
		}

		return 0;
	}

	template <typename _T>
	_T* find_global_object( const std::string& name )
	{
		typedef std::vector<std::string> string_list;

		std::string has_name;
		uclass* has_class( 0 );
		bool check_outer( false );

		string_list spaced_items;
		boost::split( spaced_items, name, boost::is_space() );

		if ( ( spaced_items.size() < 1 ) && ( spaced_items.size() > 2 ) )
			return 0;

		string_list dotted_items;

		if ( spaced_items.size() == 1 )
		{
			boost::split( dotted_items, spaced_items[0], boost::is_any_of( "." ) );
		}
		else
		{
			uname name_class( find_global_name( spaced_items[0] ) );
			has_class = find_global_object<uclass>( name_class );
			boost::split( dotted_items, spaced_items[1], boost::is_any_of( "." ) );
		}

		if ( dotted_items.size() < 1 )
			return 0;

		if ( dotted_items.size() == 1 )
		{
			has_name = dotted_items[0];
		}
		else
		{
			has_name = dotted_items.back();
			dotted_items.pop_back();
			check_outer = true;
		}

		//what would be fastest -> find index or always do a string compare...
		unsigned int has_name_index( find_global_name( has_name ).get_index() );

		for ( unsigned int i( 0 ); i < uobject::table_->size(); i++ )
		{
			uobject* object( (*uobject::table_)[i] );

			if ( object->is_valid() )
			{
				//bool same_name( boost::iequals( has_name, object->get_name() ) );
				bool same_name( has_name_index == object->get_name_index() );

				if ( same_name )
				{
					bool same_class( has_class ? object->get_class() == has_class : true );
					bool same_outer( !check_outer );

					if ( check_outer )
					{
						uobject* outer( object->get_outer() );
						string_list::reverse_iterator outer_it( dotted_items.rbegin() );

						for ( ; outer_it != dotted_items.rend(); ++outer_it )
						{
							if ( outer->is_valid() && boost::iequals( *outer_it, outer->get_name() ) )
							{
								outer = outer->get_outer();
							}
							else
							{
								break;
							}
						}

						same_outer = ( outer_it == dotted_items.rend() );
					}

					if ( same_class && same_outer )
						return reinterpret_cast<_T*>(object);
				}
			}
		}

		return 0;
	}
}

#endif
